<?php
/**
 * Copyright 2009 Joe Dotoff
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * A server socket class
 * Used to create socket in server side and wait for client connections
 *
 * @catlog   PDog
 * @package  Socket
 * @author   Joe D. <nonultimate@gmail.com>
 * @license  Apache License Version 2.0
 * @version  $Id: ServerSocket.php 33 2009-04-21 01:03:44Z nonultimate $
 */

/** Socket */
require_phc(PDOG_LIB . '/Socket/Socket.php');

class ServerSocket
{
    /**
     * Server socket instance
     *
     * @var resource
     */
    protected $_socket;
    /**
     * The address for the socket
     *
     * @var string
     */
    protected $_addr;
    /**
     * The port for the socket
     *
     * @var int
     */
    protected $_port;
    /**
     * The binding state of the socket
     *
     * @var bool
     */
    protected $_isBound;
    /**
     * Constructor
     *
     * @param int   $port     The port to listen on for the socket
     * @param int   $backlog  A maximum of backlog incoming connections will be
     *     queued for processing.
     */
    public function __construct($port = null, $backlog = null)
    {
        // Set time to infinite execution
        set_time_limit(0);
        // Try to load extension if not loaded
        if (!extension_loaded('sockets')) {
            $is_loaded = false;
            if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
                $is_loaded = dl('php_sockets.dll');
            } else {
                $is_loaded = dl('sockets.so');
            }
            if (!$is_loaded) {
                die('Sockets extension not loaded');
            }
        }
        // Initialize
        $this->_host = '';
        $this->_port = '';
        $this->_isBound = false;

        if (null !== $port and is_int($port)) {
            $this->_host = '127.0.0.1';
            $this->_port = $port;
            if ($backlog !== null and is_int($backlog))
            {
                $this->_socket = socket_create_listen($port, $backlog);
            } else {
                $this->_socket = socket_create_listen($port);
            }
            $this->_isBound = true;
        }
    }
    /**
     * Accept connections on the socket
     *
     * @return Socket
     */
    public function accept()
    {
        if (!$this->isClosed()) {
            $sock = socket_accept($this->_socket);
            $socket = new Socket();
            $socket->createFrom($sock);

            return $socket;
        }

        return null;
    }
    /**
     * Bind a name to the socket
     *
     * @param  array|object $address  keys as:
     *     'family', the protocol family, AF_INET,AF_INET6,AF_UNIX
     *     'type',   the type of the socket, SOCK_STREAM,SOCK_DGRAM,SOCK_SEQPACKET,SOCK_RAW,SOCK_RDM
     *     'proto',  the protocol of the socket, SOL_TCP,SOL_UDP
     *     'addr',   IP address or unix socket name
     *     'port',   the port for the address if it is a IP address
     * @param  int    $port       The port parameter is only used when
     *      connecting to an AF_INET socket
     * @return bool
     */
    public function bind($address)
    {
        if ($this->isBound())
        {
            return false;
        }
        if (!is_array($address) or !is_object($address)) {
            return false;
        }
        $family = AF_INET;
        $type = SOCK_STREAM;
        $proto = SOL_TCP;
        $addr = '0.0.0.0';
        try {
            foreach ($address as $key => $value) {
                $$key = $value;
            }
            $this->_addr = $addr;
            socket_create($family, $type, $proto);
            if (isset($port)) {
                $this->_port = $port;
                socket_bind($this->_socket, $addr, $port);
            } else {
                socket_bind($this->_socket, $addr);
            }
            socket_listen($this->_socket);
            $this->_isBound = true;

            return true;
        } catch (Exception $e) {
            return false;
        }
    }
    /**
     * Close the socket resource
     *
     * @return bool
     */
    public function close()
    {
        if (!$this->isClosed()) {
            socket_close($this->_socket);
        }
        $this->_socket = null;

        return true;
    }
    /**
     * Returns the local address of this server socket
     *
     * @return string
     */
    public function getAddress()
    {
        return $this->_addr;
    }
    /**
     * Returns the local address of this server socket
     *
     * @return string
     */
    public function getPort()
    {
        return $this->_port;
    }
    /**
     * Returns the binding state of the socket
     *
     * @return bool
     */
    public function isBound()
    {
        return $this->_isBound;
    }
    /**
     * Returns the close state of the socket
     *
     * @return bool
     */
    public function isClosed()
    {
        if (is_resource($this->_socket)) {
            return false;
        }

        return true;
    }
    /**
     * Accepts arrays of sockets and waits for them to change status
     *
     * @param  array  $read    The sockets listed in the read array will be watched
     *     to see if characters become available for reading
     * @param  array  $write   The sockets listed in the write array will be watched
     *     to see if a write will not block
     * @param  array  $except  The sockets listed in the except array will be watched
     *     for exceptions
     * @param  int    $sec     The timeout seconds
     * @return int
     */
    public function select(&$read, &$write, &$except, $sec)
    {
        return socket_select($read, $write, $except, $sec);
    }
}
?>